Add task_meta parameter to read_resource() for explicit task control#2750
Add task_meta parameter to read_resource() for explicit task control#2750
Conversation
Adds the same task_meta parameter pattern to resources that was added to tools, enabling explicit control over sync vs task execution with type-safe return types via overloads.
WalkthroughAdds TaskMeta-aware overloads and runtime handling across the read/run pipeline: Resource, ResourceTemplate, FunctionResourceTemplate, Tool, provider wrappers, and FastMCP.read_resource now accept an optional TaskMeta and return either synchronous results or CreateTaskResult for background-task creation. Implementations enrich task_meta.fn_key when missing, pass task_meta (with explicit arguments=None where used) into check_background_task and downstream delegations, and fall back to synchronous execution (calling read/run and converting results) when no background task is scheduled. Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/fastmcp/server/providers/fastmcp_provider.py (1)
290-319: Consider prefixinguriwith underscore to indicate intentional non-use.The static analysis tool correctly identifies that
uriis unused inFastMCPProviderResourceTemplate._read(). The method expands the original URI from_original_uri_templateandparamsinstead of using the passeduri. This is intentional sinceuriis the external/transformed URI while the child server needs the internal URI.To clarify intent and silence the linter, prefix with underscore.
🔎 Proposed fix
@overload async def _read( - self, uri: str, params: dict[str, Any], task_meta: None = None + self, _uri: str, params: dict[str, Any], task_meta: None = None ) -> ResourceResult: ... @overload async def _read( - self, uri: str, params: dict[str, Any], task_meta: TaskMeta + self, _uri: str, params: dict[str, Any], task_meta: TaskMeta ) -> mcp.types.CreateTaskResult: ... async def _read( - self, uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None + self, _uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None ) -> ResourceResult | mcp.types.CreateTaskResult:src/fastmcp/resources/template.py (1)
317-360: Consider prefixinguriwith underscore to indicate intentional non-use.Similar to the base class, the
uriparameter is unused inFunctionResourceTemplate._read()because this optimized implementation callsread(arguments=params)directly instead of creating an ephemeral resource. The static analysis tool correctly flags this.Additionally, note that the task_meta enrichment and check_background_task logic (lines 348-356) is duplicated from the base
ResourceTemplate._read(). This is acceptable here sinceFunctionResourceTemplateintentionally overrides the entire method for optimization (skipping ephemeral resource creation).🔎 Proposed fix to silence linter
@overload async def _read( - self, uri: str, params: dict[str, Any], task_meta: None = None + self, _uri: str, params: dict[str, Any], task_meta: None = None ) -> ResourceResult: ... @overload async def _read( - self, uri: str, params: dict[str, Any], task_meta: TaskMeta + self, _uri: str, params: dict[str, Any], task_meta: TaskMeta ) -> mcp.types.CreateTaskResult: ... async def _read( - self, uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None + self, _uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None ) -> ResourceResult | mcp.types.CreateTaskResult:
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (9)
tests/resources/test_function_resources.pyis excluded by none and included by nonetests/resources/test_resource_template.pyis excluded by none and included by nonetests/resources/test_resources.pyis excluded by none and included by nonetests/server/middleware/test_middleware.pyis excluded by none and included by nonetests/server/providers/test_local_provider_resources.pyis excluded by none and included by nonetests/server/tasks/test_resource_task_meta_parameter.pyis excluded by none and included by nonetests/server/test_dependencies.pyis excluded by none and included by nonetests/server/test_mount.pyis excluded by none and included by nonetests/server/test_providers.pyis excluded by none and included by none
📒 Files selected for processing (4)
src/fastmcp/resources/resource.pysrc/fastmcp/resources/template.pysrc/fastmcp/server/providers/fastmcp_provider.pysrc/fastmcp/server/server.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types
Files:
src/fastmcp/resources/resource.pysrc/fastmcp/server/server.pysrc/fastmcp/resources/template.pysrc/fastmcp/server/providers/fastmcp_provider.py
🧠 Learnings (1)
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required
Applied to files:
src/fastmcp/resources/resource.pysrc/fastmcp/resources/template.pysrc/fastmcp/server/providers/fastmcp_provider.py
🧬 Code graph analysis (3)
src/fastmcp/resources/resource.py (2)
src/fastmcp/server/tasks/config.py (2)
TaskConfig(41-120)TaskMeta(25-37)src/fastmcp/server/tasks/routing.py (1)
check_background_task(27-93)
src/fastmcp/server/server.py (6)
src/fastmcp/server/low_level.py (1)
read_resource(204-246)src/fastmcp/server/middleware/tool_injection.py (1)
read_resource(98-103)src/fastmcp/server/context.py (1)
read_resource(313-327)src/fastmcp/resources/resource.py (5)
ResourceResult(117-205)_read(304-304)_read(307-307)_read(309-345)to_mcp_result(192-205)src/fastmcp/server/tasks/config.py (1)
TaskMeta(25-37)src/fastmcp/resources/template.py (6)
_read(181-183)_read(186-188)_read(190-230)_read(318-320)_read(323-325)_read(327-360)
src/fastmcp/resources/template.py (3)
src/fastmcp/server/tasks/config.py (2)
TaskConfig(41-120)TaskMeta(25-37)src/fastmcp/resources/resource.py (5)
_read(304-304)_read(307-307)_read(309-345)ResourceResult(117-205)key(372-374)src/fastmcp/server/tasks/routing.py (1)
check_background_task(27-93)
🪛 Ruff (0.14.10)
src/fastmcp/resources/template.py
328-328: Unused method argument: uri
(ARG002)
src/fastmcp/server/providers/fastmcp_provider.py
301-301: Unused method argument: uri
(ARG002)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests with lowest-direct dependencies
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
🔇 Additional comments (13)
src/fastmcp/server/server.py (5)
1224-1248: LGTM! Well-structured overloads for type-safe return types.The overload pattern correctly narrows the return type based on
task_meta:
task_meta: None→ResourceResulttask_meta: TaskMeta→CreateTaskResultThis eliminates the need for runtime
isinstance()checks at call sites, which aligns with the PR objectives.
1270-1274: Clear and accurate documentation of the fn_key enrichment strategy.The comment correctly explains why fn_key enrichment happens in component
_read()methods rather than here — resources use the concrete URI while templates use the pattern. This design decision is consistent with the implementations inresource.pyandtemplate.py.
1285-1292: Correct middleware chain propagation of task_meta.The
task_metais correctly passed through the middleware chain via thecall_nextlambda, ensuring that task routing happens after middleware execution.
1300-1323: Proper task_meta delegation to resource and template _read() methods.Both concrete resources and templates now receive
task_metavia keyword argument, enabling consistent background task routing throughout the resource reading pipeline.
1604-1619: Correctly refactored to use explicit task_meta parameter.The handler now:
- Extracts task metadata from MCP request context (lines 1606-1611)
- Leaves
fn_keyasNonefor component-level enrichment (line 1604 comment)- Passes
task_metaexplicitly toread_resource()(line 1615)- Handles both
ResourceResultandCreateTaskResultappropriately (lines 1617-1619)This replaces the previous contextvar-based approach with explicit parameter passing.
src/fastmcp/resources/resource.py (3)
8-8: LGTM! Correct import addition.The
overloadimport fromtypingis required for the new type-narrowing overloads.
30-31: Correct import of TaskMeta.
TaskMetais appropriately imported alongsideTaskConfigfrom the tasks config module.
303-345: Well-implemented _read() with task routing and type-safe overloads.The implementation correctly:
- Uses overloads to narrow return type based on
task_metapresence- Enriches
task_meta.fn_keywithself.keywhen missing (line 335)- Passes
arguments=Nonetocheck_background_tasksince resources don't have arguments- Falls back to synchronous execution when no background task is scheduled
One observation: creating a new
TaskMetainstance for enrichment (line 335) is appropriate sinceTaskMetais a dataclass and we want to avoid mutating the caller's instance.src/fastmcp/server/providers/fastmcp_provider.py (2)
16-16: LGTM! Correct import addition.The
overloadimport is required for the new type-narrowing overloads on_readmethods.
151-173: Correct delegation with fn_key enrichment for nested mounts.The implementation correctly:
- Enriches
fn_keywithself.key(the parent's namespaced URI) before delegation- Delegates to the child server's
read_resource()with the original URI- Uses overloads for type-safe return types
This ensures that when components are mounted with namespaces, the Docket lookup key matches the registration key from
get_tasks().src/fastmcp/resources/template.py (3)
8-8: LGTM! Correct import addition.The
overloadimport fromtypingis required for the new type-narrowing overloads.
26-26: Correct import of TaskMeta.
TaskMetais appropriately imported alongsideTaskConfig.
180-230: Well-implemented _read() with proper task routing.The implementation correctly:
- Uses overloads for type-safe return types
- Enriches
fn_keywithself.key(the template pattern) when missing- Passes
paramsasargumentstocheck_background_taskfor template execution- Falls back to synchronous execution via
create_resource()+read()The comment on line 227 correctly explains why
resource.read()is called instead ofresource._read()— to avoid double task routing on the ephemeral resource.
- MCP routes no longer set fn_key - components enrich it with self.key - Provider wrappers set fn_key to parent's namespaced key before delegating - Use dataclasses.replace() instead of creating new TaskMeta instances - Add tests for mounted components with task_meta parameter
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/fastmcp/server/providers/fastmcp_provider.py (1)
192-196: Minor inconsistency: Usereplace()instead of constructor for consistency.Lines 193-194 construct a new
TaskMetadirectly instead of usingdataclasses.replace()like the other wrappers (FastMCPProviderTool._run()at line 119,FastMCPProviderResourceTemplate._read()at line 340). While functionally equivalent, usingreplace()maintains pattern consistency across the codebase.Suggested change
# Enrich fn_key with parent's URI before delegating to child if task_meta is not None and task_meta.fn_key is None: - task_meta = TaskMeta(ttl=task_meta.ttl, fn_key=self.key) + task_meta = replace(task_meta, fn_key=self.key)
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
tests/server/tasks/test_task_mount.pyis excluded by none and included by none
📒 Files selected for processing (5)
src/fastmcp/resources/resource.pysrc/fastmcp/resources/template.pysrc/fastmcp/server/providers/fastmcp_provider.pysrc/fastmcp/server/server.pysrc/fastmcp/tools/tool.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types
Files:
src/fastmcp/resources/template.pysrc/fastmcp/server/server.pysrc/fastmcp/server/providers/fastmcp_provider.pysrc/fastmcp/resources/resource.pysrc/fastmcp/tools/tool.py
🧠 Learnings (1)
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required
Applied to files:
src/fastmcp/resources/template.pysrc/fastmcp/server/providers/fastmcp_provider.pysrc/fastmcp/resources/resource.pysrc/fastmcp/tools/tool.py
🧬 Code graph analysis (3)
src/fastmcp/resources/template.py (3)
src/fastmcp/server/tasks/config.py (1)
TaskMeta(25-37)src/fastmcp/resources/resource.py (5)
_read(305-305)_read(308-308)_read(310-346)ResourceResult(118-206)key(373-375)src/fastmcp/server/tasks/routing.py (1)
check_background_task(27-93)
src/fastmcp/resources/resource.py (4)
src/fastmcp/server/context.py (1)
fastmcp(169-174)src/fastmcp/server/tasks/config.py (2)
TaskConfig(41-120)TaskMeta(25-37)src/fastmcp/resources/template.py (7)
_read(182-184)_read(187-189)_read(191-231)_read(319-321)_read(324-326)_read(328-361)key(279-281)src/fastmcp/server/tasks/routing.py (1)
check_background_task(27-93)
src/fastmcp/tools/tool.py (3)
src/fastmcp/server/providers/fastmcp_provider.py (3)
_run(90-94)_run(97-101)_run(103-123)src/fastmcp/server/tasks/config.py (1)
TaskMeta(25-37)src/fastmcp/server/tasks/routing.py (1)
check_background_task(27-93)
🪛 Ruff (0.14.10)
src/fastmcp/resources/template.py
329-329: Unused method argument: uri
(ARG002)
src/fastmcp/server/providers/fastmcp_provider.py
324-324: Unused method argument: uri
(ARG002)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (8)
src/fastmcp/tools/tool.py (1)
281-335: LGTM! Clean implementation of task_meta overloads and fn_key enrichment.The overloads correctly narrow return types based on
task_metapresence, and the fn_key enrichment pattern usingdataclasses.replace()is consistent with the approach in provider wrappers. The early return whentask_resultis truthy ensures background tasks short-circuit synchronous execution.src/fastmcp/resources/resource.py (1)
304-346: LGTM! Consistent task_meta handling for resources.The overload structure and fn_key enrichment logic mirror the Tool implementation. Passing
arguments=Noneis correct since resources don't have arguments (unlike templates which passparams).src/fastmcp/resources/template.py (1)
318-361: LGTM! Optimized template read with consistent task_meta handling.The overloads and fn_key enrichment logic are consistent with
ResourceTemplate._read(). The unuseduriparameter (flagged by Ruff) is intentional—it maintains signature compatibility with the base classResourceTemplate._read(), which uses it when creating the ephemeral resource. This subclass skips resource creation for efficiency but must preserve the interface.src/fastmcp/server/server.py (3)
1223-1264: LGTM! Well-designed overloads with clear documentation.The overloads provide proper type narrowing, and the docstring clearly explains the return type semantics. The comment at lines 1269-1273 is valuable—it explains why fn_key enrichment is delegated to components rather than done here, since resources and templates use different key formats.
1559-1572: LGTM! Clean extraction of task metadata from MCP context.The approach of extracting only
ttland leavingfn_keyunset is correct—it allows downstream components (or provider wrappers) to enrich with the appropriate key. The explanatory comment clarifies the design decision well.
1603-1624: LGTM! Consistent task_meta handling in read_resource MCP handler.The pattern mirrors
_call_tool_mcp: extract task_meta from context, pass it through, and let the component handle fn_key enrichment. The result type check at line 1622 correctly handles both sync and task paths.src/fastmcp/server/providers/fastmcp_provider.py (2)
89-123: LGTM! Provider tool wrapper correctly enriches fn_key.The overloads and enrichment logic are consistent with the pattern used in
Tool._run(). Enriching withself.key(the parent's namespaced tool name) ensures Docket uses the correctly registered lookup key.
313-342: LGTM! Template wrapper correctly enriches fn_key with parent's namespaced pattern.The overloads and enrichment logic are consistent. The unused
uriparameter (Ruff ARG002) is required for interface compatibility with the base classResourceTemplate._read()signature—this is not a defect.
|
Extends the
task_metaparameter pattern to both tools and resources, providing explicit control over sync vs task execution with type-safe return types.Previously, the return type of
read_resource()andcall_tool()was a union type requiring runtimeisinstance()checks. Now, overloads narrow the type based on whethertask_metais provided:Key changes:
read_resource()andcall_tool()accept optionaltask_metaparameter with overloads for type narrowing_call_tool_mcp,_read_resource_mcp) no longer setfn_key- components enrich it themselvesfn_keyin its_run()/_read()method usingself.keyas a fallbackfn_keywith the parent's namespaced key before delegating to child serversdataclasses.replace()for immutable TaskMeta updatestask_metaparameter usage